package org.bdd4j;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

public class Runner {

	public void executeTest(String className) throws Exception{
		///Build the metaData
		ScenarioMetaData scenario = new ScenarioMetaData();
		Class<?> klass = Class.forName(className);
		scenario.setStory(klass.getAnnotation(Story.class));
		Method[] methods = klass.getDeclaredMethods();
		for(Method m : methods){
			addGiven(m,scenario);
			addWhen(m,scenario);
			addThen(m,scenario);
		}
	
		///Run the test
		echo("SCENARIO: " + scenario.getStory().value());
		Object objectUnderTest = klass.newInstance();
		try{
			for(MetaData meta : scenario.getGiven()){
				echo("GIVEN " + meta.getMessage(),true);
				meta.getMethod().invoke(objectUnderTest);
			}
			for(MetaData meta : scenario.getWhen()){
				echo("WHEN " + meta.getMessage(),true);
				meta.getMethod().invoke(objectUnderTest);
			}
			for(MetaData meta : scenario.getThen()){
				echo("THEN " + meta.getMessage(), true);
				meta.getMethod().invoke(objectUnderTest);
			}
			echo("SCENARIO SUCCEEDED");
		}catch(BDDException e){
			echo("ERROR >> SCENARIO FAILED");
			echo(e.getMessage(), true);
		}catch(InvocationTargetException ite){
			echo("ERROR >> SCENARIO FAILED");
			echo(ite.getCause().getMessage(),true);
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	private void echo(String message){
		echo(message,false);
	}
	private void echo(String message, boolean pad){
		System.out.println(pad ? "   " + message : message);
	}
	
	private void addGiven(Method m, ScenarioMetaData s){
		try{
			Given g = m.getAnnotation(Given.class);
			MetaData meta = new MetaData(g.value(), m);
			s.addGiven(meta);
		}catch(Exception e){}
	}

	private void addThen(Method m, ScenarioMetaData s){
		try{
			Then t = m.getAnnotation(Then.class);
			MetaData meta = new MetaData(t.value(), m);
			s.addThen(meta);
		}catch(Exception e){}
	}
	
	private void addWhen(Method m,ScenarioMetaData s){
		try{
			When w = m.getAnnotation(When.class);
			MetaData meta = new MetaData(w.value(), m);
			s.addWhen(meta);
		}catch(Exception e){}
	}
	

	
	private static class ScenarioMetaData{
		private Story story;
		private List<MetaData> given = new LinkedList<MetaData>();
		private List<MetaData> when = new LinkedList<MetaData>();
		private List<MetaData> then = new LinkedList<MetaData>();
		
		public Story getStory() {
			return story;
		}
		public void setStory(Story story) {
			this.story = story;
		}
		public void addGiven(MetaData given){
			this.given.add(given);
		}
		public void addWhen(MetaData when){
			this.when.add(when);
		}
		public void addThen(MetaData then){
			this.then.add(then);
		}
		public List<MetaData> getGiven() {
			return given;
		}
		public List<MetaData> getWhen() {
			return when;
		}
		public List<MetaData> getThen() {
			return then;
		}
	}

	private static class MetaData{
		private String message;
		private Method method;
		
		public MetaData(String message, Method m) {
			super();
			this.message = message;
			this.method = m;
		}
		public String getMessage() {
			return message;
		}
		public Method getMethod() {
			return method;
		}
		
	}

	public static void main(String[] args) throws Exception{
		String className = "org.bdd4j.example.PersonFullNameFormatStory";
		Runner runner = new Runner();
		runner.executeTest(className);
	}
}
